{
 "cells": [
  {
   "cell_type": "code",
   "id": "initial_id",
   "metadata": {
    "collapsed": true,
    "ExecuteTime": {
     "end_time": "2025-02-07T02:55:48.178138Z",
     "start_time": "2025-02-07T02:55:45.959101Z"
    }
   },
   "source": [
    "import matplotlib as mpl\n",
    "import matplotlib.pyplot as plt\n",
    "%matplotlib inline\n",
    "import numpy as np\n",
    "import sklearn\n",
    "import pandas as pd\n",
    "import os\n",
    "import sys\n",
    "import time\n",
    "from tqdm.auto import tqdm\n",
    "import torch\n",
    "import torch.nn as nn\n",
    "import torch.nn.functional as F\n",
    "\n",
    "print(sys.version_info)\n",
    "for module in mpl, np, pd, sklearn, torch:\n",
    "    print(module.__name__, module.__version__)\n",
    "\n",
    "device = torch.device(\"cuda:0\") if torch.cuda.is_available() else torch.device(\"cpu\")\n",
    "print(device)\n"
   ],
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "sys.version_info(major=3, minor=12, micro=3, releaselevel='final', serial=0)\n",
      "matplotlib 3.10.0\n",
      "numpy 1.26.4\n",
      "pandas 2.2.3\n",
      "sklearn 1.6.0\n",
      "torch 2.6.0+cpu\n",
      "cpu\n"
     ]
    }
   ],
   "execution_count": 1
  },
  {
   "metadata": {},
   "cell_type": "markdown",
   "source": "# 数据准备",
   "id": "a02a9f7101d9d798"
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-02-07T02:56:22.399992Z",
     "start_time": "2025-02-07T02:56:22.391173Z"
    }
   },
   "cell_type": "code",
   "source": [
    "from sklearn.datasets import fetch_california_housing\n",
    "\n",
    "housing = fetch_california_housing(data_home=\"data\")\n",
    "print(housing.DESCR)\n",
    "print(housing.data.shape)\n",
    "print(housing.target.shape)"
   ],
   "id": "e79dff646ade2b29",
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      ".. _california_housing_dataset:\n",
      "\n",
      "California Housing dataset\n",
      "--------------------------\n",
      "\n",
      "**Data Set Characteristics:**\n",
      "\n",
      ":Number of Instances: 20640\n",
      "\n",
      ":Number of Attributes: 8 numeric, predictive attributes and the target\n",
      "\n",
      ":Attribute Information:\n",
      "    - MedInc        median income in block group\n",
      "    - HouseAge      median house age in block group\n",
      "    - AveRooms      average number of rooms per household\n",
      "    - AveBedrms     average number of bedrooms per household\n",
      "    - Population    block group population\n",
      "    - AveOccup      average number of household members\n",
      "    - Latitude      block group latitude\n",
      "    - Longitude     block group longitude\n",
      "\n",
      ":Missing Attribute Values: None\n",
      "\n",
      "This dataset was obtained from the StatLib repository.\n",
      "https://www.dcc.fc.up.pt/~ltorgo/Regression/cal_housing.html\n",
      "\n",
      "The target variable is the median house value for California districts,\n",
      "expressed in hundreds of thousands of dollars ($100,000).\n",
      "\n",
      "This dataset was derived from the 1990 U.S. census, using one row per census\n",
      "block group. A block group is the smallest geographical unit for which the U.S.\n",
      "Census Bureau publishes sample data (a block group typically has a population\n",
      "of 600 to 3,000 people).\n",
      "\n",
      "A household is a group of people residing within a home. Since the average\n",
      "number of rooms and bedrooms in this dataset are provided per household, these\n",
      "columns may take surprisingly large values for block groups with few households\n",
      "and many empty houses, such as vacation resorts.\n",
      "\n",
      "It can be downloaded/loaded using the\n",
      ":func:`sklearn.datasets.fetch_california_housing` function.\n",
      "\n",
      ".. rubric:: References\n",
      "\n",
      "- Pace, R. Kelley and Ronald Barry, Sparse Spatial Autoregressions,\n",
      "  Statistics and Probability Letters, 33 (1997) 291-297\n",
      "\n",
      "(20640, 8)\n",
      "(20640,)\n"
     ]
    }
   ],
   "execution_count": 3
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-02-07T02:56:23.831758Z",
     "start_time": "2025-02-07T02:56:23.802884Z"
    }
   },
   "cell_type": "code",
   "source": [
    "from sklearn.model_selection import train_test_split\n",
    "\n",
    "#拆分训练集和测试集，random_state是随机种子,同样的随机数种子，是为了得到同样的随机值\n",
    "x_train_all, x_test, y_train_all, y_test = train_test_split(\n",
    "    housing.data, housing.target, random_state=7)\n",
    "x_train, x_valid, y_train, y_valid = train_test_split(\n",
    "    x_train_all, y_train_all, random_state=11)\n",
    "\n",
    "dataset_maps = {\n",
    "    \"train\": [x_train, y_train],\n",
    "    \"valid\": [x_valid, y_valid],\n",
    "    \"test\": [x_test, y_test],\n",
    "}\n"
   ],
   "id": "5183e6cf8bca7343",
   "outputs": [],
   "execution_count": 4
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-02-07T02:56:29.565118Z",
     "start_time": "2025-02-07T02:56:29.558118Z"
    }
   },
   "cell_type": "code",
   "source": [
    "from sklearn.preprocessing import StandardScaler\n",
    "from torch.utils.data import DataLoader\n",
    "\n",
    "scaler = StandardScaler()\n",
    "scaler.fit(x_train)"
   ],
   "id": "787c17e43e4533c1",
   "outputs": [
    {
     "data": {
      "text/plain": [
       "StandardScaler()"
      ],
      "text/html": [
       "<style>#sk-container-id-1 {\n",
       "  /* Definition of color scheme common for light and dark mode */\n",
       "  --sklearn-color-text: #000;\n",
       "  --sklearn-color-text-muted: #666;\n",
       "  --sklearn-color-line: gray;\n",
       "  /* Definition of color scheme for unfitted estimators */\n",
       "  --sklearn-color-unfitted-level-0: #fff5e6;\n",
       "  --sklearn-color-unfitted-level-1: #f6e4d2;\n",
       "  --sklearn-color-unfitted-level-2: #ffe0b3;\n",
       "  --sklearn-color-unfitted-level-3: chocolate;\n",
       "  /* Definition of color scheme for fitted estimators */\n",
       "  --sklearn-color-fitted-level-0: #f0f8ff;\n",
       "  --sklearn-color-fitted-level-1: #d4ebff;\n",
       "  --sklearn-color-fitted-level-2: #b3dbfd;\n",
       "  --sklearn-color-fitted-level-3: cornflowerblue;\n",
       "\n",
       "  /* Specific color for light theme */\n",
       "  --sklearn-color-text-on-default-background: var(--sg-text-color, var(--theme-code-foreground, var(--jp-content-font-color1, black)));\n",
       "  --sklearn-color-background: var(--sg-background-color, var(--theme-background, var(--jp-layout-color0, white)));\n",
       "  --sklearn-color-border-box: var(--sg-text-color, var(--theme-code-foreground, var(--jp-content-font-color1, black)));\n",
       "  --sklearn-color-icon: #696969;\n",
       "\n",
       "  @media (prefers-color-scheme: dark) {\n",
       "    /* Redefinition of color scheme for dark theme */\n",
       "    --sklearn-color-text-on-default-background: var(--sg-text-color, var(--theme-code-foreground, var(--jp-content-font-color1, white)));\n",
       "    --sklearn-color-background: var(--sg-background-color, var(--theme-background, var(--jp-layout-color0, #111)));\n",
       "    --sklearn-color-border-box: var(--sg-text-color, var(--theme-code-foreground, var(--jp-content-font-color1, white)));\n",
       "    --sklearn-color-icon: #878787;\n",
       "  }\n",
       "}\n",
       "\n",
       "#sk-container-id-1 {\n",
       "  color: var(--sklearn-color-text);\n",
       "}\n",
       "\n",
       "#sk-container-id-1 pre {\n",
       "  padding: 0;\n",
       "}\n",
       "\n",
       "#sk-container-id-1 input.sk-hidden--visually {\n",
       "  border: 0;\n",
       "  clip: rect(1px 1px 1px 1px);\n",
       "  clip: rect(1px, 1px, 1px, 1px);\n",
       "  height: 1px;\n",
       "  margin: -1px;\n",
       "  overflow: hidden;\n",
       "  padding: 0;\n",
       "  position: absolute;\n",
       "  width: 1px;\n",
       "}\n",
       "\n",
       "#sk-container-id-1 div.sk-dashed-wrapped {\n",
       "  border: 1px dashed var(--sklearn-color-line);\n",
       "  margin: 0 0.4em 0.5em 0.4em;\n",
       "  box-sizing: border-box;\n",
       "  padding-bottom: 0.4em;\n",
       "  background-color: var(--sklearn-color-background);\n",
       "}\n",
       "\n",
       "#sk-container-id-1 div.sk-container {\n",
       "  /* jupyter's `normalize.less` sets `[hidden] { display: none; }`\n",
       "     but bootstrap.min.css set `[hidden] { display: none !important; }`\n",
       "     so we also need the `!important` here to be able to override the\n",
       "     default hidden behavior on the sphinx rendered scikit-learn.org.\n",
       "     See: https://github.com/scikit-learn/scikit-learn/issues/21755 */\n",
       "  display: inline-block !important;\n",
       "  position: relative;\n",
       "}\n",
       "\n",
       "#sk-container-id-1 div.sk-text-repr-fallback {\n",
       "  display: none;\n",
       "}\n",
       "\n",
       "div.sk-parallel-item,\n",
       "div.sk-serial,\n",
       "div.sk-item {\n",
       "  /* draw centered vertical line to link estimators */\n",
       "  background-image: linear-gradient(var(--sklearn-color-text-on-default-background), var(--sklearn-color-text-on-default-background));\n",
       "  background-size: 2px 100%;\n",
       "  background-repeat: no-repeat;\n",
       "  background-position: center center;\n",
       "}\n",
       "\n",
       "/* Parallel-specific style estimator block */\n",
       "\n",
       "#sk-container-id-1 div.sk-parallel-item::after {\n",
       "  content: \"\";\n",
       "  width: 100%;\n",
       "  border-bottom: 2px solid var(--sklearn-color-text-on-default-background);\n",
       "  flex-grow: 1;\n",
       "}\n",
       "\n",
       "#sk-container-id-1 div.sk-parallel {\n",
       "  display: flex;\n",
       "  align-items: stretch;\n",
       "  justify-content: center;\n",
       "  background-color: var(--sklearn-color-background);\n",
       "  position: relative;\n",
       "}\n",
       "\n",
       "#sk-container-id-1 div.sk-parallel-item {\n",
       "  display: flex;\n",
       "  flex-direction: column;\n",
       "}\n",
       "\n",
       "#sk-container-id-1 div.sk-parallel-item:first-child::after {\n",
       "  align-self: flex-end;\n",
       "  width: 50%;\n",
       "}\n",
       "\n",
       "#sk-container-id-1 div.sk-parallel-item:last-child::after {\n",
       "  align-self: flex-start;\n",
       "  width: 50%;\n",
       "}\n",
       "\n",
       "#sk-container-id-1 div.sk-parallel-item:only-child::after {\n",
       "  width: 0;\n",
       "}\n",
       "\n",
       "/* Serial-specific style estimator block */\n",
       "\n",
       "#sk-container-id-1 div.sk-serial {\n",
       "  display: flex;\n",
       "  flex-direction: column;\n",
       "  align-items: center;\n",
       "  background-color: var(--sklearn-color-background);\n",
       "  padding-right: 1em;\n",
       "  padding-left: 1em;\n",
       "}\n",
       "\n",
       "\n",
       "/* Toggleable style: style used for estimator/Pipeline/ColumnTransformer box that is\n",
       "clickable and can be expanded/collapsed.\n",
       "- Pipeline and ColumnTransformer use this feature and define the default style\n",
       "- Estimators will overwrite some part of the style using the `sk-estimator` class\n",
       "*/\n",
       "\n",
       "/* Pipeline and ColumnTransformer style (default) */\n",
       "\n",
       "#sk-container-id-1 div.sk-toggleable {\n",
       "  /* Default theme specific background. It is overwritten whether we have a\n",
       "  specific estimator or a Pipeline/ColumnTransformer */\n",
       "  background-color: var(--sklearn-color-background);\n",
       "}\n",
       "\n",
       "/* Toggleable label */\n",
       "#sk-container-id-1 label.sk-toggleable__label {\n",
       "  cursor: pointer;\n",
       "  display: flex;\n",
       "  width: 100%;\n",
       "  margin-bottom: 0;\n",
       "  padding: 0.5em;\n",
       "  box-sizing: border-box;\n",
       "  text-align: center;\n",
       "  align-items: start;\n",
       "  justify-content: space-between;\n",
       "  gap: 0.5em;\n",
       "}\n",
       "\n",
       "#sk-container-id-1 label.sk-toggleable__label .caption {\n",
       "  font-size: 0.6rem;\n",
       "  font-weight: lighter;\n",
       "  color: var(--sklearn-color-text-muted);\n",
       "}\n",
       "\n",
       "#sk-container-id-1 label.sk-toggleable__label-arrow:before {\n",
       "  /* Arrow on the left of the label */\n",
       "  content: \"▸\";\n",
       "  float: left;\n",
       "  margin-right: 0.25em;\n",
       "  color: var(--sklearn-color-icon);\n",
       "}\n",
       "\n",
       "#sk-container-id-1 label.sk-toggleable__label-arrow:hover:before {\n",
       "  color: var(--sklearn-color-text);\n",
       "}\n",
       "\n",
       "/* Toggleable content - dropdown */\n",
       "\n",
       "#sk-container-id-1 div.sk-toggleable__content {\n",
       "  max-height: 0;\n",
       "  max-width: 0;\n",
       "  overflow: hidden;\n",
       "  text-align: left;\n",
       "  /* unfitted */\n",
       "  background-color: var(--sklearn-color-unfitted-level-0);\n",
       "}\n",
       "\n",
       "#sk-container-id-1 div.sk-toggleable__content.fitted {\n",
       "  /* fitted */\n",
       "  background-color: var(--sklearn-color-fitted-level-0);\n",
       "}\n",
       "\n",
       "#sk-container-id-1 div.sk-toggleable__content pre {\n",
       "  margin: 0.2em;\n",
       "  border-radius: 0.25em;\n",
       "  color: var(--sklearn-color-text);\n",
       "  /* unfitted */\n",
       "  background-color: var(--sklearn-color-unfitted-level-0);\n",
       "}\n",
       "\n",
       "#sk-container-id-1 div.sk-toggleable__content.fitted pre {\n",
       "  /* unfitted */\n",
       "  background-color: var(--sklearn-color-fitted-level-0);\n",
       "}\n",
       "\n",
       "#sk-container-id-1 input.sk-toggleable__control:checked~div.sk-toggleable__content {\n",
       "  /* Expand drop-down */\n",
       "  max-height: 200px;\n",
       "  max-width: 100%;\n",
       "  overflow: auto;\n",
       "}\n",
       "\n",
       "#sk-container-id-1 input.sk-toggleable__control:checked~label.sk-toggleable__label-arrow:before {\n",
       "  content: \"▾\";\n",
       "}\n",
       "\n",
       "/* Pipeline/ColumnTransformer-specific style */\n",
       "\n",
       "#sk-container-id-1 div.sk-label input.sk-toggleable__control:checked~label.sk-toggleable__label {\n",
       "  color: var(--sklearn-color-text);\n",
       "  background-color: var(--sklearn-color-unfitted-level-2);\n",
       "}\n",
       "\n",
       "#sk-container-id-1 div.sk-label.fitted input.sk-toggleable__control:checked~label.sk-toggleable__label {\n",
       "  background-color: var(--sklearn-color-fitted-level-2);\n",
       "}\n",
       "\n",
       "/* Estimator-specific style */\n",
       "\n",
       "/* Colorize estimator box */\n",
       "#sk-container-id-1 div.sk-estimator input.sk-toggleable__control:checked~label.sk-toggleable__label {\n",
       "  /* unfitted */\n",
       "  background-color: var(--sklearn-color-unfitted-level-2);\n",
       "}\n",
       "\n",
       "#sk-container-id-1 div.sk-estimator.fitted input.sk-toggleable__control:checked~label.sk-toggleable__label {\n",
       "  /* fitted */\n",
       "  background-color: var(--sklearn-color-fitted-level-2);\n",
       "}\n",
       "\n",
       "#sk-container-id-1 div.sk-label label.sk-toggleable__label,\n",
       "#sk-container-id-1 div.sk-label label {\n",
       "  /* The background is the default theme color */\n",
       "  color: var(--sklearn-color-text-on-default-background);\n",
       "}\n",
       "\n",
       "/* On hover, darken the color of the background */\n",
       "#sk-container-id-1 div.sk-label:hover label.sk-toggleable__label {\n",
       "  color: var(--sklearn-color-text);\n",
       "  background-color: var(--sklearn-color-unfitted-level-2);\n",
       "}\n",
       "\n",
       "/* Label box, darken color on hover, fitted */\n",
       "#sk-container-id-1 div.sk-label.fitted:hover label.sk-toggleable__label.fitted {\n",
       "  color: var(--sklearn-color-text);\n",
       "  background-color: var(--sklearn-color-fitted-level-2);\n",
       "}\n",
       "\n",
       "/* Estimator label */\n",
       "\n",
       "#sk-container-id-1 div.sk-label label {\n",
       "  font-family: monospace;\n",
       "  font-weight: bold;\n",
       "  display: inline-block;\n",
       "  line-height: 1.2em;\n",
       "}\n",
       "\n",
       "#sk-container-id-1 div.sk-label-container {\n",
       "  text-align: center;\n",
       "}\n",
       "\n",
       "/* Estimator-specific */\n",
       "#sk-container-id-1 div.sk-estimator {\n",
       "  font-family: monospace;\n",
       "  border: 1px dotted var(--sklearn-color-border-box);\n",
       "  border-radius: 0.25em;\n",
       "  box-sizing: border-box;\n",
       "  margin-bottom: 0.5em;\n",
       "  /* unfitted */\n",
       "  background-color: var(--sklearn-color-unfitted-level-0);\n",
       "}\n",
       "\n",
       "#sk-container-id-1 div.sk-estimator.fitted {\n",
       "  /* fitted */\n",
       "  background-color: var(--sklearn-color-fitted-level-0);\n",
       "}\n",
       "\n",
       "/* on hover */\n",
       "#sk-container-id-1 div.sk-estimator:hover {\n",
       "  /* unfitted */\n",
       "  background-color: var(--sklearn-color-unfitted-level-2);\n",
       "}\n",
       "\n",
       "#sk-container-id-1 div.sk-estimator.fitted:hover {\n",
       "  /* fitted */\n",
       "  background-color: var(--sklearn-color-fitted-level-2);\n",
       "}\n",
       "\n",
       "/* Specification for estimator info (e.g. \"i\" and \"?\") */\n",
       "\n",
       "/* Common style for \"i\" and \"?\" */\n",
       "\n",
       ".sk-estimator-doc-link,\n",
       "a:link.sk-estimator-doc-link,\n",
       "a:visited.sk-estimator-doc-link {\n",
       "  float: right;\n",
       "  font-size: smaller;\n",
       "  line-height: 1em;\n",
       "  font-family: monospace;\n",
       "  background-color: var(--sklearn-color-background);\n",
       "  border-radius: 1em;\n",
       "  height: 1em;\n",
       "  width: 1em;\n",
       "  text-decoration: none !important;\n",
       "  margin-left: 0.5em;\n",
       "  text-align: center;\n",
       "  /* unfitted */\n",
       "  border: var(--sklearn-color-unfitted-level-1) 1pt solid;\n",
       "  color: var(--sklearn-color-unfitted-level-1);\n",
       "}\n",
       "\n",
       ".sk-estimator-doc-link.fitted,\n",
       "a:link.sk-estimator-doc-link.fitted,\n",
       "a:visited.sk-estimator-doc-link.fitted {\n",
       "  /* fitted */\n",
       "  border: var(--sklearn-color-fitted-level-1) 1pt solid;\n",
       "  color: var(--sklearn-color-fitted-level-1);\n",
       "}\n",
       "\n",
       "/* On hover */\n",
       "div.sk-estimator:hover .sk-estimator-doc-link:hover,\n",
       ".sk-estimator-doc-link:hover,\n",
       "div.sk-label-container:hover .sk-estimator-doc-link:hover,\n",
       ".sk-estimator-doc-link:hover {\n",
       "  /* unfitted */\n",
       "  background-color: var(--sklearn-color-unfitted-level-3);\n",
       "  color: var(--sklearn-color-background);\n",
       "  text-decoration: none;\n",
       "}\n",
       "\n",
       "div.sk-estimator.fitted:hover .sk-estimator-doc-link.fitted:hover,\n",
       ".sk-estimator-doc-link.fitted:hover,\n",
       "div.sk-label-container:hover .sk-estimator-doc-link.fitted:hover,\n",
       ".sk-estimator-doc-link.fitted:hover {\n",
       "  /* fitted */\n",
       "  background-color: var(--sklearn-color-fitted-level-3);\n",
       "  color: var(--sklearn-color-background);\n",
       "  text-decoration: none;\n",
       "}\n",
       "\n",
       "/* Span, style for the box shown on hovering the info icon */\n",
       ".sk-estimator-doc-link span {\n",
       "  display: none;\n",
       "  z-index: 9999;\n",
       "  position: relative;\n",
       "  font-weight: normal;\n",
       "  right: .2ex;\n",
       "  padding: .5ex;\n",
       "  margin: .5ex;\n",
       "  width: min-content;\n",
       "  min-width: 20ex;\n",
       "  max-width: 50ex;\n",
       "  color: var(--sklearn-color-text);\n",
       "  box-shadow: 2pt 2pt 4pt #999;\n",
       "  /* unfitted */\n",
       "  background: var(--sklearn-color-unfitted-level-0);\n",
       "  border: .5pt solid var(--sklearn-color-unfitted-level-3);\n",
       "}\n",
       "\n",
       ".sk-estimator-doc-link.fitted span {\n",
       "  /* fitted */\n",
       "  background: var(--sklearn-color-fitted-level-0);\n",
       "  border: var(--sklearn-color-fitted-level-3);\n",
       "}\n",
       "\n",
       ".sk-estimator-doc-link:hover span {\n",
       "  display: block;\n",
       "}\n",
       "\n",
       "/* \"?\"-specific style due to the `<a>` HTML tag */\n",
       "\n",
       "#sk-container-id-1 a.estimator_doc_link {\n",
       "  float: right;\n",
       "  font-size: 1rem;\n",
       "  line-height: 1em;\n",
       "  font-family: monospace;\n",
       "  background-color: var(--sklearn-color-background);\n",
       "  border-radius: 1rem;\n",
       "  height: 1rem;\n",
       "  width: 1rem;\n",
       "  text-decoration: none;\n",
       "  /* unfitted */\n",
       "  color: var(--sklearn-color-unfitted-level-1);\n",
       "  border: var(--sklearn-color-unfitted-level-1) 1pt solid;\n",
       "}\n",
       "\n",
       "#sk-container-id-1 a.estimator_doc_link.fitted {\n",
       "  /* fitted */\n",
       "  border: var(--sklearn-color-fitted-level-1) 1pt solid;\n",
       "  color: var(--sklearn-color-fitted-level-1);\n",
       "}\n",
       "\n",
       "/* On hover */\n",
       "#sk-container-id-1 a.estimator_doc_link:hover {\n",
       "  /* unfitted */\n",
       "  background-color: var(--sklearn-color-unfitted-level-3);\n",
       "  color: var(--sklearn-color-background);\n",
       "  text-decoration: none;\n",
       "}\n",
       "\n",
       "#sk-container-id-1 a.estimator_doc_link.fitted:hover {\n",
       "  /* fitted */\n",
       "  background-color: var(--sklearn-color-fitted-level-3);\n",
       "}\n",
       "</style><div id=\"sk-container-id-1\" class=\"sk-top-container\"><div class=\"sk-text-repr-fallback\"><pre>StandardScaler()</pre><b>In a Jupyter environment, please rerun this cell to show the HTML representation or trust the notebook. <br />On GitHub, the HTML representation is unable to render, please try loading this page with nbviewer.org.</b></div><div class=\"sk-container\" hidden><div class=\"sk-item\"><div class=\"sk-estimator fitted sk-toggleable\"><input class=\"sk-toggleable__control sk-hidden--visually\" id=\"sk-estimator-id-1\" type=\"checkbox\" checked><label for=\"sk-estimator-id-1\" class=\"sk-toggleable__label fitted sk-toggleable__label-arrow\"><div><div>StandardScaler</div></div><div><a class=\"sk-estimator-doc-link fitted\" rel=\"noreferrer\" target=\"_blank\" href=\"https://scikit-learn.org/1.6/modules/generated/sklearn.preprocessing.StandardScaler.html\">?<span>Documentation for StandardScaler</span></a><span class=\"sk-estimator-doc-link fitted\">i<span>Fitted</span></span></div></label><div class=\"sk-toggleable__content fitted\"><pre>StandardScaler()</pre></div> </div></div></div></div>"
      ]
     },
     "execution_count": 5,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "execution_count": 5
  },
  {
   "metadata": {},
   "cell_type": "markdown",
   "source": "# 构建数据集",
   "id": "277604fe5c887627"
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-02-07T02:57:55.950628Z",
     "start_time": "2025-02-07T02:57:55.941769Z"
    }
   },
   "cell_type": "code",
   "source": [
    "# 从PyTorch的utils.data模块导入Dataset类，并定义一个名为HousingDataset的新类，继承自Dataset。\n",
    "# Dataset是PyTorch中用于表示数据集的抽象基类\n",
    "# 通过继承Dataset类，我们可以自定义数据集的行为，使其与PyTorch的数据加载器兼容。\n",
    "from torch.utils.data import Dataset\n",
    "\n",
    "\n",
    "class HousingDataset(Dataset):\n",
    "\n",
    "    # 初始化方法，根据指定的模式加载数据，并对数据进行预处理\n",
    "    def __init__(self, mode='train'):\n",
    "        self.x, self.y = dataset_maps[mode]\n",
    "        self.x = torch.from_numpy(scaler.transform(self.x)).float()\n",
    "        self.y = torch.from_numpy(self.y).float().reshape(-1, 1)\n",
    "\n",
    "    def __len__(self):\n",
    "        return len(self.x)\n",
    "\n",
    "    def __getitem__(self, idx):\n",
    "        return self.x[idx], self.y[idx]\n",
    "\n",
    "\n",
    "train_ds = HousingDataset(\"train\")\n",
    "valid_ds = HousingDataset(\"valid\")\n",
    "test_ds = HousingDataset(\"test\")"
   ],
   "id": "39f153f6a146831e",
   "outputs": [],
   "execution_count": 6
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-02-07T02:58:03.243478Z",
     "start_time": "2025-02-07T02:58:03.235784Z"
    }
   },
   "cell_type": "code",
   "source": "train_ds[1]",
   "id": "b18d34f2e2b7b871",
   "outputs": [
    {
     "data": {
      "text/plain": [
       "(tensor([-0.2981,  0.3523, -0.1092, -0.2506, -0.0341, -0.0060,  1.0806, -1.0611]),\n",
       " tensor([1.5140]))"
      ]
     },
     "execution_count": 7,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "execution_count": 7
  },
  {
   "metadata": {},
   "cell_type": "markdown",
   "source": "# DataLoader",
   "id": "49bfd82a5f35a6db"
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-02-07T02:58:27.593900Z",
     "start_time": "2025-02-07T02:58:27.591021Z"
    }
   },
   "cell_type": "code",
   "source": [
    "from torch.utils.data import DataLoader\n",
    "\n",
    "batch_size = 8\n",
    "train_loader = DataLoader(train_ds, batch_size=batch_size, shuffle=False)\n",
    "val_loader = DataLoader(valid_ds, batch_size=batch_size, shuffle=False)\n",
    "test_loader = DataLoader(test_ds, batch_size=batch_size, shuffle=False)"
   ],
   "id": "89733d12cad9e256",
   "outputs": [],
   "execution_count": 8
  },
  {
   "metadata": {},
   "cell_type": "markdown",
   "source": [
    "## 定义模型\n",
    "\n",
    "多输出模型常见于多任务学习（Multi-Task Learning）中，一个任务会有一个输出层，计算一个loss，最后多个任务的loss联合训练模型。除此之外，如果想拿到模型的中间输出，也可以使用多输出。\n",
    "\n",
    "例如，这里想拿到 deep_output，构建多输出模型"
   ],
   "id": "25858736192ecaaf"
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-02-07T03:10:07.310358Z",
     "start_time": "2025-02-07T03:10:07.305359Z"
    }
   },
   "cell_type": "code",
   "source": [
    "import torch\n",
    "import torch.nn as nn\n",
    "\n",
    "\n",
    "class WideDeep(nn.Module):\n",
    "    \"\"\"Wide & Deep 学习模型的多输出变体实现\n",
    "    同时结合线性模型的记忆能力和深度神经网络的泛化能力\n",
    "    适用于推荐系统等需要同时处理显式和隐式特征的场景\n",
    "    \n",
    "    Args:\n",
    "        input_dim (int): 输入特征的维度，默认8维\n",
    "    \"\"\"\n",
    "\n",
    "    def __init__(self, input_dim=8):\n",
    "        super().__init__()  # 继承父类初始化\n",
    "\n",
    "        # Deep部分：深度特征提取模块\n",
    "        self.deep = nn.Sequential(\n",
    "            nn.Linear(input_dim, 30),  # 第一全连接层（输入→30维）\n",
    "            nn.ReLU(),  # 非线性激活函数\n",
    "            nn.Linear(30, 30),  # 第二全连接层（30→30维）\n",
    "            nn.ReLU()  # 保持特征非线性表达能力\n",
    "        )\n",
    "\n",
    "        # 联合输出层（Wide + Deep特征融合）\n",
    "        self.output_layer = nn.Linear(30 + input_dim, 1)  # 拼接后维度 → 1维输出\n",
    "\n",
    "        # 参数初始化\n",
    "        self.init_weights()\n",
    "\n",
    "    def init_weights(self):\n",
    "        \"\"\"使用 Xavier 均匀初始化策略\n",
    "        优点：\n",
    "        - 保持各层激活值的方差一致性\n",
    "        - 防止梯度消失/爆炸\n",
    "        - 特别适合 ReLU 激活函数\n",
    "        \"\"\"\n",
    "        for m in self.modules():\n",
    "            if isinstance(m, nn.Linear):\n",
    "                nn.init.xavier_uniform_(m.weight)  # 权重初始化\n",
    "                nn.init.zeros_(m.bias)  # 偏置归零\n",
    "\n",
    "    def forward(self, x, return_deep_output=False):\n",
    "        \"\"\"前向传播流程\n",
    "        \n",
    "        Args:\n",
    "            x (Tensor): 输入特征张量 [batch_size, input_dim]\n",
    "            return_deep_output (bool): 是否返回中间层特征\n",
    "            \n",
    "        Returns:\n",
    "            默认返回预测值 logits\n",
    "            当 return_deep_output=True 时返回 (logits, deep_features)\n",
    "        \"\"\"\n",
    "        # Deep特征提取 [batch,8] → [batch,30]\n",
    "        deep_output = self.deep(x)\n",
    "\n",
    "        # Wide&Deep特征拼接：原始特征 + 深层特征\n",
    "        concat = torch.cat([x, deep_output], dim=-1)  # [batch,8+30=38]\n",
    "\n",
    "        # 最终预测输出 [batch,38] → [batch,1]\n",
    "        logits = self.output_layer(concat)\n",
    "\n",
    "        # 多输出模式控制\n",
    "        return (logits, deep_output) if return_deep_output else logits"
   ],
   "id": "e171bbaa79a2c07a",
   "outputs": [],
   "execution_count": 17
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-02-07T03:07:07.142934Z",
     "start_time": "2025-02-07T03:07:07.140070Z"
    }
   },
   "cell_type": "code",
   "source": [
    "class EarlyStopCallback:\n",
    "    def __init__(self, patience=5, min_delta=0.01):\n",
    "        self.patience = patience  #容忍度\n",
    "        self.min_delta = min_delta  #最小变化量\n",
    "        self.best_metric = -1  #最佳指标\n",
    "        self.counter = 0  #计数器\n",
    "\n",
    "    def __call__(self, metric):\n",
    "        if metric >= self.best_metric + self.min_delta:\n",
    "            self.best_metric = metric  #更新最佳指标\n",
    "            self.counter = 0  #计数器清零\n",
    "        else:\n",
    "            self.counter += 1  #计数器加一\n",
    "\n",
    "    @property  #定义一个属性，用于判断是否满足early stop条件\n",
    "    def early_stop(self):\n",
    "        return self.counter >= self.patience"
   ],
   "id": "2230ba2bcf326c62",
   "outputs": [],
   "execution_count": 10
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-02-07T03:07:29.054826Z",
     "start_time": "2025-02-07T03:07:29.050583Z"
    }
   },
   "cell_type": "code",
   "source": [
    "from sklearn.metrics import accuracy_score\n",
    "\n",
    "\n",
    "@torch.no_grad()  #不计算梯度\n",
    "def evaluating(model, dataloader, loss_fct):\n",
    "    loss_list = []  #损失列表\n",
    "    for datas, labels in dataloader:  # 遍历验证集\n",
    "        datas = datas.to(device)\n",
    "        labels = labels.to(device)\n",
    "        # 前向计算\n",
    "        logits = model(datas)\n",
    "        loss = loss_fct(logits, labels)  # 验证集损失\n",
    "        loss_list.append(loss.item())  # 损失列表添加损失值\n",
    "\n",
    "    return np.mean(loss_list)  # 计算平均损失值"
   ],
   "id": "915a3e0f40a1a45b",
   "outputs": [],
   "execution_count": 11
  },
  {
   "metadata": {},
   "cell_type": "markdown",
   "source": "# 训练模型",
   "id": "e9a171b713370c69"
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-02-07T03:08:35.763590Z",
     "start_time": "2025-02-07T03:08:20.547635Z"
    }
   },
   "cell_type": "code",
   "source": [
    "# 训练\n",
    "def training(\n",
    "        model,\n",
    "        train_loader,\n",
    "        val_loader,\n",
    "        epoch,\n",
    "        loss_fct,\n",
    "        optimizer,\n",
    "        tensorboard_callback=None,\n",
    "        save_ckpt_callback=None,\n",
    "        early_stop_callback=None,\n",
    "        eval_step=500,\n",
    "):\n",
    "    record_dict = {\n",
    "        \"train\": [],\n",
    "        \"val\": []\n",
    "    }\n",
    "\n",
    "    global_step = 0\n",
    "    model.train()\n",
    "    with tqdm(total=epoch * len(train_loader)) as pbar:\n",
    "        for epoch_id in range(epoch):\n",
    "            # training\n",
    "            for datas, labels in train_loader:\n",
    "                datas = datas.to(device)\n",
    "                labels = labels.to(device)\n",
    "                # 梯度清空\n",
    "                optimizer.zero_grad()\n",
    "                # 模型前向计算\n",
    "                logits, deep_output = model(datas, return_deep_output=True)\n",
    "                deep_output = deep_output.mean(axis=1).reshape(-1,\n",
    "                                                               1)  # # 平均池化,尺寸为[batch size, 30]，求平均就变为[batch size],reshape成[batch size, 1]\n",
    "                logits = logits + deep_output  # 尺寸一致，相加，求损失\n",
    "                # 计算损失\n",
    "                loss = loss_fct(logits, labels)\n",
    "                # 梯度回传\n",
    "                loss.backward()\n",
    "                # 调整优化器，包括学习率的变动等\n",
    "                optimizer.step()\n",
    "\n",
    "                loss = loss.cpu().item()\n",
    "                # record\n",
    "\n",
    "                record_dict[\"train\"].append({\n",
    "                    \"loss\": loss, \"step\": global_step\n",
    "                })\n",
    "\n",
    "                # evaluating\n",
    "                if global_step % eval_step == 0:\n",
    "                    model.eval()\n",
    "                    val_loss = evaluating(model, val_loader, loss_fct)\n",
    "                    record_dict[\"val\"].append({\n",
    "                        \"loss\": val_loss, \"step\": global_step\n",
    "                    })\n",
    "                    model.train()\n",
    "\n",
    "                    # 早停 Early Stop\n",
    "                    if early_stop_callback is not None:\n",
    "                        early_stop_callback(-val_loss)\n",
    "                        if early_stop_callback.early_stop:\n",
    "                            print(f\"Early stop at epoch {epoch_id} / global_step {global_step}\")\n",
    "                            return record_dict\n",
    "\n",
    "                # udate step\n",
    "                global_step += 1\n",
    "                pbar.update(1)\n",
    "                pbar.set_postfix({\"epoch\": epoch_id})\n",
    "\n",
    "    return record_dict\n",
    "\n",
    "\n",
    "epoch = 10\n",
    "\n",
    "model = WideDeep()\n",
    "\n",
    "# 1. 定义损失函数 采用MSE损失\n",
    "loss_fct = nn.MSELoss()\n",
    "# 2. 定义优化器 采用SGD\n",
    "# Optimizers specified in the torch.optim package\n",
    "optimizer = torch.optim.SGD(model.parameters(), lr=0.001, momentum=0.0)\n",
    "\n",
    "# 3. early stop\n",
    "early_stop_callback = EarlyStopCallback(patience=10, min_delta=1e-3)\n",
    "\n",
    "model = model.to(device)\n",
    "record = training(\n",
    "    model,\n",
    "    train_loader,\n",
    "    val_loader,\n",
    "    epoch,\n",
    "    loss_fct,\n",
    "    optimizer,\n",
    "    early_stop_callback=early_stop_callback,\n",
    "    eval_step=len(train_loader)\n",
    ")"
   ],
   "id": "a44e949130b391f2",
   "outputs": [
    {
     "data": {
      "text/plain": [
       "  0%|          | 0/14520 [00:00<?, ?it/s]"
      ],
      "application/vnd.jupyter.widget-view+json": {
       "version_major": 2,
       "version_minor": 0,
       "model_id": "74fe3581495b42f6ac47a8b049cfc25d"
      }
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "IOPub message rate exceeded.\n",
      "The Jupyter server will temporarily stop sending output\n",
      "to the client in order to avoid crashing it.\n",
      "To change this limit, set the config variable\n",
      "`--ServerApp.iopub_msg_rate_limit`.\n",
      "\n",
      "Current values:\n",
      "ServerApp.iopub_msg_rate_limit=1000.0 (msgs/sec)\n",
      "ServerApp.rate_limit_window=3.0 (secs)\n",
      "\n",
      "IOPub message rate exceeded.\n",
      "The Jupyter server will temporarily stop sending output\n",
      "to the client in order to avoid crashing it.\n",
      "To change this limit, set the config variable\n",
      "`--ServerApp.iopub_msg_rate_limit`.\n",
      "\n",
      "Current values:\n",
      "ServerApp.iopub_msg_rate_limit=1000.0 (msgs/sec)\n",
      "ServerApp.rate_limit_window=3.0 (secs)\n",
      "\n",
      "IOPub message rate exceeded.\n",
      "The Jupyter server will temporarily stop sending output\n",
      "to the client in order to avoid crashing it.\n",
      "To change this limit, set the config variable\n",
      "`--ServerApp.iopub_msg_rate_limit`.\n",
      "\n",
      "Current values:\n",
      "ServerApp.iopub_msg_rate_limit=1000.0 (msgs/sec)\n",
      "ServerApp.rate_limit_window=3.0 (secs)\n",
      "\n",
      "IOPub message rate exceeded.\n",
      "The Jupyter server will temporarily stop sending output\n",
      "to the client in order to avoid crashing it.\n",
      "To change this limit, set the config variable\n",
      "`--ServerApp.iopub_msg_rate_limit`.\n",
      "\n",
      "Current values:\n",
      "ServerApp.iopub_msg_rate_limit=1000.0 (msgs/sec)\n",
      "ServerApp.rate_limit_window=3.0 (secs)\n",
      "\n"
     ]
    }
   ],
   "execution_count": 12
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-02-07T03:08:35.829378Z",
     "start_time": "2025-02-07T03:08:35.764594Z"
    }
   },
   "cell_type": "code",
   "source": [
    "#画线要注意的是损失是不一定在零到1之间的\n",
    "def plot_learning_curves(record_dict, sample_step=500):\n",
    "    # build DataFrame\n",
    "    train_df = pd.DataFrame(record_dict[\"train\"]).set_index(\"step\").iloc[::sample_step]\n",
    "    val_df = pd.DataFrame(record_dict[\"val\"]).set_index(\"step\")\n",
    "\n",
    "    # plot\n",
    "    for idx, item in enumerate(train_df.columns):\n",
    "        plt.plot(train_df.index, train_df[item], label=f\"train_{item}\")\n",
    "        plt.plot(val_df.index, val_df[item], label=f\"val_{item}\")\n",
    "        plt.grid()\n",
    "        plt.legend()\n",
    "        # plt.xticks(range(0, train_df.index[-1], 10*sample_step), range(0, train_df.index[-1], 10*sample_step))\n",
    "        plt.xlabel(\"step\")\n",
    "\n",
    "        plt.show()\n",
    "\n",
    "\n",
    "plot_learning_curves(record, sample_step=500)  #横坐标是 steps"
   ],
   "id": "f1706d1319ac70a8",
   "outputs": [
    {
     "data": {
      "text/plain": [
       "<Figure size 640x480 with 1 Axes>"
      ],
      "image/png": "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//d//2f6ce/evRtXXnmlGldcXBw6d+6MxYsXe74vNdWPPPIIGjZsqL4/ePBgbNy4scR9HD58GFdccYVqoJOcnIzrrrsO2dnZJW6zYsUKnHrqqer/Qjr5PffccwiV4uJiPPzww2jRooUaU8uWLfH444+X2HfALOP+888/cc4556gOhfKc/u6770p8P5jj/Oqrr9CuXTt1G3meTZ06NSTjlv0v7r//fnUM8fHx6jZXX3216sRs5nGXdvPNN6vbvPzyyzV+3OXSTODLL7/UoqOjtQ8//FBbvXq1dsMNN2jJycna/v37tZrgzDPP1D766CNt1apV2rJly7Thw4drTZs21bKzsz23ufnmm7W0tDTt999/1xYvXqydfPLJWv/+/T3fLyoq0jp16qQNHjxY++eff7SpU6dqKSkp2oMPPui5zZYtWzSHw6Hdfffd2po1a7TXXntNi4iI0H755Rct1BYuXKg1b95c69Kli3bnnXeaetyHDx/WmjVrpo0ZM0ZbsGCBOr5ff/1V27Rpk+c2zzzzjFarVi3tu+++05YvX66de+65WosWLbRjx455bnPWWWdpXbt21ebPn6/NmTNHa9WqlTZy5EjP9zMyMrQGDRpoV1xxhXpuffHFF1pcXJz2zjvvaKHw5JNPanXr1tV++uknbevWrdpXX32lJSQkaK+88orpxi3Pw4ceekj79ttvJWrSpkyZUuL7wRrn33//rZ7rzz33nHru//e//9WioqK0lStXBn3cR48eVf+nkyZN0tatW6fNmzdP69Onj9azZ88S92G2cXuT78vYGjVqpL300ks1ftzlMUVgIU/O2267zfN1cXGx+sM9/fTTWk104MAB9eScPXu25x9SnhzyQqxbu3atuo38c+pPbLvdru3bt89zm7feektLSkrS8vPz1df33Xef1rFjxxK/67LLLlOBTShlZWVprVu31qZPn66dfvrpnsDCrOO+//77tVNOOaXc7zudTi01NVV7/vnnPdfJYxETE6NeTIS8aMjjsGjRIs9tpk2bptlsNm337t3q6zfffFOrXbu253HQf3fbtm21UBgxYoR27bXXlrjuwgsvVC+UZh536TeaYI7z0ksvVY+7t759+2o33XSTFmgVvcF6f6CQ223fvt304961a5fWuHFjFRTIBwvvwMIM4/ZW46dCCgoKsGTJEpVK9N6PRL6eN28eaqKM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"
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "execution_count": 13
  },
  {
   "metadata": {},
   "cell_type": "markdown",
   "source": "# 评估模型",
   "id": "ae6c36e5805461e"
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-02-07T03:08:41.444279Z",
     "start_time": "2025-02-07T03:08:41.402585Z"
    }
   },
   "cell_type": "code",
   "source": [
    "model.eval()\n",
    "loss = evaluating(model, val_loader, loss_fct)\n",
    "print(f\"loss:     {loss:.4f}\")"
   ],
   "id": "a573bd4d907c6b68",
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "loss:     0.4731\n"
     ]
    }
   ],
   "execution_count": 14
  },
  {
   "metadata": {},
   "cell_type": "markdown",
   "source": "# 中间输出",
   "id": "26cf17406dc8e4fe"
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-02-07T03:09:31.588415Z",
     "start_time": "2025-02-07T03:09:31.579733Z"
    }
   },
   "cell_type": "code",
   "source": [
    "logits, deep_output = model(train_ds[:][0].to(device), return_deep_output=True)\n",
    "deep_output.shape"
   ],
   "id": "4d65cf0656648e67",
   "outputs": [
    {
     "data": {
      "text/plain": [
       "torch.Size([11610, 30])"
      ]
     },
     "execution_count": 15,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "execution_count": 15
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-02-07T03:09:34.470759Z",
     "start_time": "2025-02-07T03:09:34.443743Z"
    }
   },
   "cell_type": "code",
   "source": [
    "# 从这看到deep部分抽取到的特征分布，有些特征没那么重要，或许可以消减一些神经元\n",
    "plt.imshow(deep_output.cpu().detach().numpy().mean(axis=0).reshape(1, -1))\n",
    "plt.yticks([])\n",
    "plt.show()"
   ],
   "id": "fee3e9a705ec6a03",
   "outputs": [
    {
     "data": {
      "text/plain": [
       "<Figure size 640x480 with 1 Axes>"
      ],
      "image/png": "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"
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "execution_count": 16
  },
  {
   "metadata": {},
   "cell_type": "code",
   "outputs": [],
   "execution_count": null,
   "source": "",
   "id": "c198b53d240074ac"
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 2
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython2",
   "version": "2.7.6"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
